6.2 Lineare Regression mit Scikit-Learn#

Im vorherigen Kapitel haben wir die theoretischen Grundlagen des lineare Regressionsmodells kennengelernt. Nun werden wir anhand eines praktischen Datensatzes das lineare Regressionmodell mit dem Modul Scikit-Learn ausprobieren.

Lernziele#

Lernziele

  • Sie können erste grundlegende Schritte der Datenvorverarbeitung anwenden:

    • Sie können unvollständige Daten mit dropna aus dem Datensatz entfernen.

    • Sie können Ausreißer mit drop entfernen.

    • Sie können eine Eigenschaft als Input auswählen und mit reshape in Matrixform bringen.

  • Sie können ein lineares Regressionsmodell aus Scikit-Learn laden und mit fit trainieren.

  • Sie können mit dem trainierten Modell und predict eine Prognose abgeben.

Deutscher Gebrauchtwagenmarkt (Autoscout24)#

Question: Angenommen, Sie besitzen ein Auto, möchten es aber verkaufen. Für wieviel Euro sollten Sie Ihr Auto zum Verkauf anbieten?

Understanding the data: Um diese Frage zu beantworten, sammeln wir zuerst Daten von Auto-Verkaufspreisen und erkunden diese.

Dazu benutzen wir einen Datensatz über den deutschen Gebrauchtwagenmarkt von 2011 bis 2021 (Autoscout24). Der Datensatz stammt von Kaggle. Enthalten sind Daten zu den Merkmalen

  • mileage: kilometres traveled by the vehicle (= Kilometerstand)

  • make: make of the car (= Marke)

  • model: model of the car (= Modell)

  • fuel: fuel type (= Treibstoffart)

  • gear: manual or automatic (= Getriebe)

  • offerType: type of offer (new, used, …) (= Angebotsart)

  • price: sale price of the vehicle (= Gebrauchtpreis)

  • hp: horse power (= PS)

  • year: the vehicle registration year (= Baujahr)

Wie immer laden wir die Daten und verschaffen uns zunächst einen Überblick.

import pandas as pd

data_raw = pd.read_csv('data/autoscout24-germany-dataset.csv')
data_raw.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 46405 entries, 0 to 46404
Data columns (total 9 columns):
 #   Column     Non-Null Count  Dtype  
---  ------     --------------  -----  
 0   mileage    46405 non-null  int64  
 1   make       46405 non-null  object 
 2   model      46262 non-null  object 
 3   fuel       46405 non-null  object 
 4   gear       46223 non-null  object 
 5   offerType  46405 non-null  object 
 6   price      46405 non-null  int64  
 7   hp         46376 non-null  float64
 8   year       46405 non-null  int64  
dtypes: float64(1), int64(3), object(5)
memory usage: 3.2+ MB

Offensichtlich sind in den Spalten ‘model’, ‘gear’ und ‘hp’ einige Datensätze nicht vollständig. Das erkennen wir daran, dass insgesmt 46.405 Einträge vorliegen, aber in diesen drei Spalten weniger erfasst sind.

Wir machen es uns jetzt einfach und entfernen die nicht vollständigen Daten aus unserem Datensatz mit der Methode .dropna(), siehe Pandas-Dokumentation → dropna). Bei einem echten Industrieprojekt müssten wir dem Problem nachgehen und die fehlenden Daten beschaffen. Sollte das nicht gehen, so müssten wir als nächstes analysieren, warum die Daten fehlen, ob beispielsweise eine Systematik dahintersteckt, und uns dann einen geeigneten Plan machen, wie mit den fehlenden Daten umzugehen ist. Das ist ein eigenständiges Thema innerhalb des ML, auf das wir im späteren Verlauf der Vorlesung noch näher eingehen werden.

data = data_raw.dropna().copy()
data.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 46071 entries, 0 to 46404
Data columns (total 9 columns):
 #   Column     Non-Null Count  Dtype  
---  ------     --------------  -----  
 0   mileage    46071 non-null  int64  
 1   make       46071 non-null  object 
 2   model      46071 non-null  object 
 3   fuel       46071 non-null  object 
 4   gear       46071 non-null  object 
 5   offerType  46071 non-null  object 
 6   price      46071 non-null  int64  
 7   hp         46071 non-null  float64
 8   year       46071 non-null  int64  
dtypes: float64(1), int64(3), object(5)
memory usage: 3.5+ MB

In dem Datensatz gibt es nur vier Eigenschaften, die numerisch sind, also metrische oder quantitative Merkmale repräsentieren. Wir wählen die Eigenschaft Preis als Zielgröße (=abhängige Variable oder Wirkung oder Output oder Target).

Als nächstes visualisieren wir den Preis abhängig von den Merkmalen

  • Kilometerstand,

  • Baujahr und

  • PS.

Wir fangen mit dem Kilometerstand der Autos an.

import plotly.express as px

fig = px.scatter(data, x = 'mileage', y = 'price', title='Gebrauchtwagenmarkt 2011-2021 (Autoscout24)')
fig.update_layout(
    xaxis_title = 'Kilometerstand [km]',
    yaxis_title = 'Preis (Euro)'
)
fig.show()

Sieht nicht besonders linear aus, eher wie eine Hyperbel. Als nächstes betrachten wir den Preis in Abhängigkeit des Baujahrs.

fig = px.scatter(data, x = 'year', y = 'price', title='Gebrauchtwagenmarkt 2011-2021 (Autoscout24)')
fig.update_layout(
    xaxis_title = 'Baujahr',
    yaxis_title = 'Preis (Euro)'
)
fig.show()

Je jünger, desto teurer, könnte linear sein. Und zuletzt visualisieren wir den Preis abhängig von der PS-Zahl.

fig = px.scatter(data, x = 'hp', y = 'price', title='Gebrauchtwagenmarkt 2011-2021 (Autoscout24)')
fig.update_layout(
    xaxis_title = 'Leistung (PS)',
    yaxis_title = 'Preis (Euro)'
)
fig.show()

Bei dem Input PS scheint es eine lineare Abhängigkeit zu geben. Je mehr PS desto teurer. Wir wählen daher als Merkmal für unsere lineare Regression die PS-Zahl aus.

Training des linearen Regressionsmodell#

So wie wir Pandas zur Verwaltung der Daten nutzen, so verwenden wir das Modul Scikit-Learn für den ML-Part. Scikit-Learn ist eine der beliebtesten Open-Source-Bibliotheken für maschinelles Lernen in Python. Ein großer Vorteil von Scikit-Learn ist, dass die dort integrierten ML-Modelle stets dieselbe Schnittstelle bieten. Die Dokumentation findet sich hier:

Das Modul der Bibiolthek Scikit-Learn hat den Namen sklearn. Lineare ML-Modelle fasst Scikit-Learn in einem Untermodul namens linear_model zusammen. Um also das lineare Regressionsmodell LinearRegression verwenden zu können, müssen wir es folgendermaßen importieren und initialisieren:

from sklearn.linear_model import LinearRegression

# Algorithm selection
model = LinearRegression()
/opt/homebrew/Caskroom/miniconda/base/envs/python310/lib/python3.10/site-packages/scipy/__init__.py:146: UserWarning:

A NumPy version >=1.16.5 and <1.23.0 is required for this version of SciPy (detected version 1.25.0

Mit der Methode .fit() werden die Parameter des Modells an die Daten angepasst. Dazu müssen die Daten in einem bestimmten Format vorliegen. Bei den Inputs wird davon ausgegangen, dass mehrere Eigenschaften in das Modell eingehen sollen. Die Eigenschaften stehen normalerweise in den Spalten des Datensatzes. Beim Output erwarten wir zunächst nur eine Eigenschaft, die durch das Modell erklärt werden soll. Daher geht Scikit-Learn davon aus, dass der Input eine Tabelle (Matrix) \(X\) ist, die M Zeilen und N Spalten hat. M ist die Anzahl an Datenpunkten, hier also die Anzahl der Autos, und N ist die Anzahl der Merkmale, die betrachtet werden sollen. Da wir momentan nur die Abhängigkeit des Preises von der PS-Zahl analysieren wollen, ist \(N=1\). Beim Output geht Scikit-Learn davon aus, dass eine Datenreihe (eindimensionaler Spaltenvektor) vorliegt, die natürlich ebenfalls M Zeilen hat. Wir müssen daher unsere PS-Zahlen noch in das Matrix-Format bringen. Dazu verwenden wir den Trick, dass mit [ [list] ] eine Tabelle extrahiert wird.

# Adaption of the data
X = data[['hp']]
y = data['price']

# Algorithm training
model.fit(X, y);

Es erfolgt keine Ausgabe, aber jetzt ist das lineare Regressionsmodell trainiert. Die durch das Training bestimmten Parameter des Modells sind im Modell selbst abgespeichert. Bei dem linearen Regressionsmodell sind das die beiden Parameter \(w_0\) und \(w_1\), also Steigung .coef_ und den y-Achsenabschnitt .intercept_.

print(f'Steigung: {model.coef_}')
print(f'y-Achsenabschnitt: {model.intercept_}')
Steigung: [191.76073729]
y-Achsenabschnitt: -8939.6499591744

Bewertung des Modell#

Im vorherigen Kapitel haben wir das R2-Bestimmtheitsmaß kennengelernt, das bewertet, wie gut das lineare Regressionsmodell prognostiziert. In Scikit-Learn werden die Bewertungsmaße über die Methode .score() ermittelt.

# Validierung
r2 = model.score(X, y)
print('Der R2-Score ist: {:.2f}'.format(r2))
Der R2-Score ist: 0.56

Ein R²-Score von 0.56 ist nicht besonders gut. Das lineare Regressionsmodell hat keine Hyperparameter, die feinjustiert werden könnten. Zur Bewertung lassen wir das Resultat visualisieren.

Damit brauchen wir eine Wertetabelle für PS-Zahlen von 0 bis 800 PS und die Prognose des Modells für diese PS-Zahlen. Zunächst erzeugen 100 PS-Zahlen von 0 bis 800 PS. Dazu nutzen wir aus dem NumPy-Modul den Befehl linspace(start, stopp, anzahl_punkte).

import numpy as np

ps_zahlen = np.linspace(0, 800, 100)
print(ps_zahlen)
[  0.           8.08080808  16.16161616  24.24242424  32.32323232
  40.4040404   48.48484848  56.56565657  64.64646465  72.72727273
  80.80808081  88.88888889  96.96969697 105.05050505 113.13131313
 121.21212121 129.29292929 137.37373737 145.45454545 153.53535354
 161.61616162 169.6969697  177.77777778 185.85858586 193.93939394
 202.02020202 210.1010101  218.18181818 226.26262626 234.34343434
 242.42424242 250.50505051 258.58585859 266.66666667 274.74747475
 282.82828283 290.90909091 298.98989899 307.07070707 315.15151515
 323.23232323 331.31313131 339.39393939 347.47474747 355.55555556
 363.63636364 371.71717172 379.7979798  387.87878788 395.95959596
 404.04040404 412.12121212 420.2020202  428.28282828 436.36363636
 444.44444444 452.52525253 460.60606061 468.68686869 476.76767677
 484.84848485 492.92929293 501.01010101 509.09090909 517.17171717
 525.25252525 533.33333333 541.41414141 549.49494949 557.57575758
 565.65656566 573.73737374 581.81818182 589.8989899  597.97979798
 606.06060606 614.14141414 622.22222222 630.3030303  638.38383838
 646.46464646 654.54545455 662.62626263 670.70707071 678.78787879
 686.86868687 694.94949495 703.03030303 711.11111111 719.19191919
 727.27272727 735.35353535 743.43434343 751.51515152 759.5959596
 767.67676768 775.75757576 783.83838384 791.91919192 800.        ]

Das trainierte Modell erwartet Daten in demselben Format, mit dem es trainiert wurde. Daher erstellen wir nun mit dem NumPy-Array einen Pandas-DataFrame und lassen dann das trainierte Modell die Preise prognostizieren.

X_predict = pd.DataFrame(ps_zahlen, columns=['hp'])
y_predict = model.predict(X_predict)

import plotly.graph_objects as go

fig = go.Figure()

fig.add_trace(go.Scatter(x = data['hp'], y = data['price'], mode='markers', name='Daten'))
fig.add_trace(go.Scatter(x = X_predict['hp'], y = y_predict, mode='lines', name='Prognose'))
fig.update_layout(
  title='Gebrauchtwagenmarkt 2011-2021 (Autoscout24)',
  xaxis_title = 'Leistung (PS)',
  yaxis_title = 'Preis (Euro)'
)
fig.show()

Damit könnten wir die Geradengleichung

\[y = 191.76073729 \cdot x + 8939.6499591744\]

aufstellen und eine Funktion implementieren, um für eine PS-Zahl eine Prognose abzugeben, welchen Verkauspreis das Auto erzielen könnte. Aber tatsächlich hat das Scikit-Learn für uns schon erledigt. Die Methode .predict() berechnet mit den intern gespeicherten Koeffizienten des linearen Regressionsmodells eine Prognose. Für eine PS-Zahl von 80 PS wird ein Verkaufspreis von

model.predict([[80]])
/opt/homebrew/Caskroom/miniconda/base/envs/python310/lib/python3.10/site-packages/sklearn/base.py:450: UserWarning:

X does not have valid feature names, but LinearRegression was fitted with feature names
array([6401.20902423])

6.614 EUR erzielt. Denken Sie daran, dass eine Matrix als Input übergeben werden muss, daher die doppelten eckigen Klammern.

Zusammenfassung#

In diesem Abschnitt haben Sie gelernt, dass das Training eines linearen Regressionsmodells darauf beruht, die Fehlerquadratsumme zu minimieren. Um überhaupt beurteilen zu können, ob ein ML-Modell geeignet ist, brauchen wir Qualitätskriterien. Für das lineare Regressionsmodell dient das Bestimmtheitsmaß bzw. der R²-Score als Qualitätskriterium.